﻿package {
	//可视化相关类
	import flash.display.MovieClip;
	import flash.display.Sprite;
	//事件类
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	//加载相关类
	import flash.net.URLRequest;
	import flash.net.URLLoader;
	//文本相关类
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	//声音相关类
	import flash.media.SoundChannel;
	import flash.media.Sound;
	//系统设置类
	import flash.system.System;

	//类及变量声明
	//======================================================================================//
	public class LRC extends MovieClip {
		//歌词容器
		private var lrcMC:Sprite=new Sprite;
		//卡拉OK显示方式遮照
		private var lrc_mask:Sprite=new Sprite;
		//歌词文本框
		private var lrc_txt:TextField=new TextField;
		//播放时间文本框
		private var time_txt:TextField=new TextField;
		//用于存储全部歌词内容(每行为一项)
		private var lrcAllArr:Array;
		//歌词文件
		private var _lrcURL:URLRequest;
		//加载歌词对象
		private var lrcLoader:URLLoader;
		//与歌词相匹配的正在播放的mp3歌曲（为一个声音对象）
		private var _mp3:SoundChannel;
		//正在播放歌曲Sound
		private var _playSound:Sound;
		//歌词时间判断计数器
		private var lrcTimeChk:Timer;
		//歌词显示时的记数器，用于显示效果
		private var lrcListTimer:Timer;
		//歌词的显示风格,默认为方式0
		private var style:uint=0;
		//与时间匹配的歌词
		private var lrcbody:String;
		//卡拉OK形式累加数
		private var OKi:uint;
		//歌词显示范围（横向）
		private var lrcMaskWidth=300;
		//修正歌词显示
		private var reviseLRC:int=0;
		//歌曲总时间
		private var songTime:Number;

		//======================================================================================//
		//构造函数
		public function LRC():void {
			init();
		}
		//初始化
		private function init():void {
			//解决编码问题
			System.useCodePage=true;
			//歌词文本框
			lrc_txt.autoSize=TextFieldAutoSize.LEFT;
			lrc_txt.selectable=false;
			lrc_txt.text="";
			lrcMC.addChild(lrc_txt);
			addChild(lrcMC);
			//时间文本框
			time_txt.selectable=false;
			lrcListTimer=new Timer(50,10);
			//加载歌词文件
			lrcLoader=new URLLoader  ;
		}
		//
		//======================================================================================//
		/**
		//提供给外部设置的属性
		**/
		//================================================》
		//歌词显示风格
		public function set LRCstyle(LRCstyle:uint):void {
			style=LRCstyle;
		}
		//歌词显示时间修正
		public function set reviseTimeLRC(_reviseLRC:uint):void {
			reviseLRC=_reviseLRC;
		}
		public function get reviseTimeLRC():uint {
			return reviseLRC;
		}
		//歌词显示区域宽
		public function set LRCwidth(LRCwidth:Number):void {
			lrcMaskWidth=LRCwidth;
			//歌词显示范围遮照
			addChild(lrc_mask);//这句很关键，因为只有这样遮照才能和下边的MC一起移动
			lrc_mask.graphics.beginFill(0xFFFFFF);
			lrc_mask.graphics.drawRect(0,0,lrcMaskWidth,17);
			lrc_mask.graphics.endFill();
			//绑定给歌词文本框容器
			lrcMC.mask=lrc_mask;
		}
		//歌词文本框的文本
		public function set txt(_txt:String) {
			lrcListTimer.stop();
			lrcMC.x=lrcMC.y=0;
			lrc_txt.htmlText="<font color='#ffa800'>" + _txt + "</font>";
		}
		//  
		//======================================================================================//
		/**
		//提供给外部的方法
		**/
		//================================================》
		//时间显示
		public function getPlayTime():TextField {
			return time_txt;
		}
		//================================================》
		//提供给外部使用的显示歌词方法，需要传入两个参数，一个正在播放的SoundChannel对象和歌词文件
		public function listLRC(playSound:Sound,mp3:SoundChannel,lrcURL:String):void {
			lrcListTimer.stop();
			lrcMC.y=0;
			lrcMC.x=0;
			//正在加载歌词
			lrc_txt.htmlText="<font color='#ffa800'>正在读取,请稍后。。。</font>";
			//清空歌词数组
			lrcAllArr=[];
			//获得正在播放的歌曲(SoundChannel对象，且于后面监听)
			_mp3=mp3;
			_playSound=playSound;
			//将传入的URL转换为URLRequest对象
			_lrcURL=new URLRequest(lrcURL);
			lrcLoader.load(_lrcURL);
			//加载完成时调用
			lrcLoader.addEventListener(Event.COMPLETE,loadEnd);
			//加载错误时调用
			lrcLoader.addEventListener(IOErrorEvent.IO_ERROR,loadErr);
		}
		// 
		//======================================================================================//
		/**
		以下为类内部函数
		**/
		//================================================》
		//加载歌词出错
		private function loadErr(evt:IOErrorEvent):void {
			lrcAllArr=[];
			var formatLrc:TextFormat=new TextFormat;
			formatLrc.color=0xFF0000;
			formatLrc.font="Arial";
			lrc_txt.text="没有找到相关歌词";
			lrc_txt.setTextFormat(formatLrc);
		}
		//================================================》
		//当歌词加载完成时调用
		private function loadEnd(event:Event):void {
			//lrcLoader.close()
			lrcAllArr=[];
			//歌曲名
			var tiExp:RegExp=/ti:[^\r]+\r/g;
			//歌手
			var arExp:RegExp=/ar:[^\r]+\r/g;
			//以一行为一单位取得所有歌词与时间信息
			var rowExp:RegExp=/\[\d{2}[^\r]+\r/g;
			//不是］开头直到换行取得歌词
			var lrcExp:RegExp=/[^\]]+\r/g;
			//格式为00:00取得时间
			var timeExp:RegExp=/\d{2}:\d{2}/g;
			//将有效歌词以每行为单位存入临时数组
			var ti:String=event.target.data.match(tiExp).toString();
			ti=ti.substring(3,ti.length - 2);
			var ar:String=event.target.data.match(arExp).toString();
			ar=ar.substring(3,ar.length - 2);
			//如果加载成功先显示歌名
			lrc_txt.htmlText="<font color='#ffa800'>" + ti + "</font>";
			//加入歌曲名与歌手信息
			lrcAllArr.push({time:"00:00",lrc:"【" + ti + "】" + "～" + "【" + ar + "】\r"});
			var tmpArr=event.target.data.match(rowExp);
			//将临时数组的每项分解成对象并存入歌词数组
			for each (var lrc in tmpArr) {
				//对每一行中进行分解取得时间（可能存在多个时间）
				var tmptime:Array=lrc.match(timeExp);
				//对每一行中进行分解取得歌词
				var tmplrc:String=lrc.match(lrcExp).toString();
				//将空歌词转换为......
				tmplrc=tmplrc == ""?tmplrc="～～～～MUSIC～～～～\r":tmplrc;
				//如果存在多个时间，将时间分解后存入数组
				for(var i=0;i<tmptime.length;i++) {;
				lrcAllArr.push({time:tmptime[i],lrc:tmplrc});
			}//END FOR
		}//END FOR
		//对歌词数给按时间排序
		lrcAllArr.sortOn(["time"]);
		for each (var tmp in lrcAllArr) {
			trace(tmp.time + ":" + tmp.lrc);
		}
		//每0.1秒检查一次歌词时间是否与当前歌曲播放时间相等
		lrcTimeChk=new Timer(100,0);
		lrcTimeChk.addEventListener(TimerEvent.TIMER,chkLRC);
		lrcTimeChk.start();
		//歌曲总时间
		var loaded:int=_playSound.bytesLoaded;
		var total:int=_playSound.bytesTotal;
		var loadbfb:Number=loaded / total;
		songTime=_playSound.length / loadbfb;
	}
	//================================================》
	//歌词时间匹配检查函数，每0.1秒检查一次
	private function chkLRC(evt:TimerEvent):void {
		//获得当前播放歌曲的当前播放头位置
		var mp3Sec:uint=Math.floor(_mp3.position / 1000);
		//歌曲播放时的同步时间
		var listSec:String=mp3Sec % 60 < 10?"0" + mp3Sec % 60:(mp3Sec % 60).toString();
		var listMin:String=Math.floor(mp3Sec / 60) < 10?"0" + Math.floor(mp3Sec / 60).toString():Math.floor(mp3Sec / 60).toString();
		//time_txt.htmlText="<FONT color='#FFFFFF'>"+listMin+":"+listSec+"/"+songTime+"</FONT>";
		time_txt.htmlText="<FONT color='#ffa800'>" + listMin + ":" + listSec + "</FONT>";
		//修正歌词显示
		mp3Sec=mp3Sec - reviseLRC < 0?0:mp3Sec - reviseLRC;
		//修正后时间
		var mp3Times:String;
		//修正秒数的格式，即不足10为0x
		var Sec:String=mp3Sec % 60 < 10?"0" + mp3Sec % 60:(mp3Sec % 60).toString();
		//取得分数
		var mp3Min:uint=Math.floor(mp3Sec / 60);
		//修正分数的格式，即不足10为0x
		var Min:String=mp3Min < 10?"0" + mp3Min:mp3Min.toString();
		//计算出歌曲播放时的同步时间
		mp3Times=Min + ":" + Sec;
		//检查歌词数组中的每一项是否有与当前时间相匹配
		for (var lrcN=0; lrcN < lrcAllArr.length; lrcN++) {
			//逐个比较
			if (lrcAllArr[lrcN].time == mp3Times) {
				lrcbody=lrcAllArr[lrcN].lrc;
				//根据风格调用相关歌词显示效果
				switch (style) {
					case 1 :
						lrcList1(lrcN);
						break;
					default :
						lrcList0(lrcbody,lrcN);
				}//END SWITCH
			}//END IF
		}//END FOR
	}//END FUN
	//================================================》
	//单行卡拉OK形式展现歌词
	private function lrcList0(lrcbody:String,lrcN:uint):void {
		//还原歌词容器坐标（切换风格及移动歌词后修正需要）
		lrcMC.y=0;
		lrcMC.x=0;
		//防止读取超出歌词数组
		if (lrcN < lrcAllArr.length - 1) {
			//取得下一个歌词的显示时间
			var nextTime=lrcAllArr[lrcN + 1].time;
			//取得当前歌词的显示时间
			var thisTime=lrcAllArr[lrcN].time;
			//取完成后从歌词数组中删除当前的歌词及时间
			lrcAllArr.splice(lrcN,1);
			//计算出前后两次歌词显示时间差
			var timeing:int=Number(nextTime.substr(-2,2)) - Number(thisTime.substr(-2,2));
			//在分钟转换处修正时间差
			timeing=timeing < 0?timeing + 60:timeing;
		} else {
			//最后一句的假设时间
			timeing=10;
			lrcAllArr.splice(lrcN,1);
			//计算出前后两次歌词显示时间差
		}
		//普通歌词字体格式
		var formatLrc:TextFormat=new TextFormat;
		formatLrc.color=0xd1d1d1;
		formatLrc.font="Arial";
		//显示歌词
		lrc_txt.text=lrcbody;
		//设置普通歌词格式
		lrc_txt.setTextFormat(formatLrc);
		//歌词显示速度（根据前面的时间差和当前歌词的长度而定）
		var speed:uint=Math.floor(timeing * 1000 / lrcbody.length) - 20;
		//卡拉OK歌词字符索引
		OKi=1;
		//卡拉OK显示效果计时器
		lrcListTimer=new Timer(speed,lrc_txt.length);
		lrcListTimer.addEventListener(TimerEvent.TIMER,lrcOK);
		lrcListTimer.start();
	}
	//================================================》
	//卡拉OK效果显示
	private function lrcOK(evt:Event):void {
		//突出显示的卡拉OK歌词字体格式
		var formatOKLrc:TextFormat=new TextFormat;
		formatOKLrc.color=0xffa800;
		//字符索引超出范围修正
		var n=OKi > lrc_txt.length?n=lrc_txt.length:OKi;
		//根据字符索引显示卡拉OK歌词
		lrc_txt.setTextFormat(formatOKLrc,0,n);
		OKi++;
		//超出显示区则移动显示
		if (lrc_txt.width > lrcMaskWidth && lrcMC.x > - (lrc_txt.width - lrcMaskWidth)) {
			//移动速度为（歌词范围长度－显示范围/歌词的字符长度）
			var speed=Math.floor((lrc_txt.width-lrcMaskWidth)/lrc_txt.length)+1;
			lrcMC.x-= speed;
		}
	}
	//================================================》
	//垂直滚动风格展示歌词
	private function lrcList1(lrcN:uint):void {
		//还原歌词容器坐标（切换风格及移动歌词后修正需要）
		lrcMC.x=0;
		//假设一个歌词长度以实现歌词只被加文本框加载一次
		//因为一般一句歌词不会超过50，而一首歌词则不会少于50
		if (lrc_txt.length < 50) {
			var _lrc:String="";
			//取出歌词数组中每一项并转换成字符串
			for each (var lrc in lrcAllArr) {
				_lrc+= lrc.lrc;
			}
			//字体格式
			var formatLrc:TextFormat=new TextFormat;
			formatLrc.color=0xffa800;
			formatLrc.leading=2;
			formatLrc.font="Arial";
			lrc_txt.autoSize=TextFieldAutoSize.LEFT;
			//显示歌词
			lrc_txt.text=_lrc;
			//设置普通歌词格式
			lrc_txt.setTextFormat(formatLrc);
			//滚动位置至当前歌词的前一行（因为数组索引都从0开始所以需要-1修正）
			//其中17为行高距离
			lrcMC.y=-(lrcN-1)*17;
		}
		//取完成后从歌词数组中删除当前的歌词及时间
		lrcAllArr.splice(lrcN,1);
		//滚动计时器
		lrcListTimer=new Timer(50,10);
		lrcListTimer.addEventListener(TimerEvent.TIMER,lrcMove);
		lrcListTimer.start();
	}
	//================================================》
	//每次滚动1/10行高，滚动10次
	private function lrcMove(evt:Event):void {
		lrcMC.y-= 1.71;
		trace(lrcMC.y);
	}
}
}